Project Overzicht
Technische specificaties en architectuur van het Interactieve Escape Dozen systeem
Concept
Vijf interactieve dozen met unieke sensor-uitdagingen. Spelers moeten alle sensoren correct activeren om escape codes te verkrijgen.
- 5 Arduino-gestuurde dozen
- 4 dozen met verschillende sensortypes
- 1 doos met een code-invoersysteem
- Volledige draadloze communicatie tussen de puzzels
Hardware
Gebaseerd op Arduino WiFi Kit 32 met verschillende sensoren voor een complete gebruikerservaring.
- Arduino WiFi Kit 32 controller
- OLED display voor gebruikersinterface
- Push-buttons voor code-invoer
- MPU6050 accelerometer
- LDR lichtsensor
- Microfoon
- Temperatuursensor
Software
Arduino code met state machine architectuur voor betrouwbare sensor verwerking en gebruikersinteractie.
- State machine-logica
- Sensorkalibratie
- OLED display-controle
- Interrupt-handling
- Webserver
Technische Schema's
Uitgebreide blokdiagrammen en circuits ontworpen met Fritzing
Blokschema Knoppen
Fritzing 
            Interactief knoppensysteem met OLED-display voor code-invoer, buzzer voor audio-feedback en webserver voor communicatie
Blokschema Beweging
Fritzing 
            Accelerometer-gebaseerd systeem voor bewegingsdetectie met MPU6050-sensor
Blokschema Temperatuur
Fritzing 
            Temperatuur-sensing systeem met automatische kalibratie en threshold-detectie
Blokschema Licht
Fritzing 
            LDR-gebaseerd lichtsensorsysteem voor ambient light-detectie
Blokschema Geluid
Fritzing 
            Microfoon-gebaseerd geluidssensorsysteem voor decibelniveau-detectie (>80dB)
Code Documentatie
Volledig gecommenteerde broncode met uitleg van algoritmen en implementatie
Knoppen Game Master (server_knoppen.ino)
#include <Arduino.h>
#include <U8g2lib.h>
#include <Wire.h>
#include <WebServer.h>
#include "WiFi.h"
#include "HT_SSD1306Wire.h"
// Display configuratie
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE);
SSD1306Wire  display(0x3c, 500000, SDA_OLED, SCL_OLED, GEOMETRY_128_64, RST_OLED);
// Pin configuratie voor knoppen
const uint8_t digitPins[4] = { 1, 3, 5, 7 };
const uint8_t okPin  = 20;
const int buzzer = 46;
const int8_t X_OFFSET = 5;
// Debounce variabelen
bool lastState[5];
uint32_t lastDebounceTime[5] = { 0, 0, 0, 0 };
const uint16_t DEBOUNCE_MS = 20;
// Game variabelen
typedef uint8_t byte;
byte digits[4] = { 1, 1, 1, 1 };
byte secret[4] = { 1, 2, 3, 4 };
// Netwerk configuratie
WebServer server(80);
const char *host = "1.1.1.1";
WiFiClient client;
bool isConnected = false;
// Display posities
const int X0      =  0;
const int Y_LINE1 = 10;
const int Y_LINE2 = 30;
const int Y_LINE3 = 50;
// Buzzer variabelen
bool buzzerActive = false;
uint8_t buzzCount = 0;
uint8_t buzzTarget = 0;
unsigned long lastBuzzTime = 0;
bool buzzerOn = false;
const uint16_t buzzInterval = 200;
// Functie voor het verwerken van inkomende data
void onDataReceived() {
  String Payload = server.arg("data");
  int commaIndex = Payload.indexOf('_');
  if (commaIndex == -1) return;
  int number = Payload.substring(0, commaIndex).toInt();
  String text = Payload.substring(commaIndex + 1);
  Serial.println("Extracted number: " + String(number));
  Serial.println("Extracted string: " + text);
  if (text == "complete" && number >= 0 && number < 4) {
    // Start buzzer feedback
    buzzTarget = secret[number];
    buzzCount = 0;
    buzzerActive = false;
    buzzerOn = false;
    noTone(buzzer);
    delay(50);
    buzzerActive = true;
    lastBuzzTime = millis();
    Serial.println("Starting non-blocking buzz for " + String(buzzTarget) + " times");
  }
}
void setup() {
  Serial.begin(115200);
  Serial.println("Initializing game master");
  Serial.begin(115200);
  // Display initialiseren
  display.init();
  display.setFont(ArialMT_Plain_10);
  display.clear();
  display.display();
  // Knoppen configureren als INPUT_PULLUP
  for (uint8_t i = 0; i < 4; i++) {
    pinMode(digitPins[i], INPUT_PULLUP);
    lastState[i] = HIGH;
  }
  pinMode(okPin, INPUT_PULLUP);
  lastState[4] = HIGH;
  for (uint8_t i = 0; i < 4; i++) {
    pinMode(digitPins[i], INPUT_PULLUP);
    lastState[i] = HIGH;
  }
  pinMode(okPin, INPUT_PULLUP);
  pinMode(buzzer, OUTPUT);
  lastState[4] = HIGH;
  u8g2.begin();
  u8g2.setFont(u8g2_font_6x10_tf);
  // Random geheime code genereren
  for (uint8_t i = 0; i < 4; i++) {
    secret[i] = random(1, 10);
  }
  // WiFi configureren als Access Point
  WiFi.disconnect(true);
  
  WiFi.mode(WIFI_MODE_APSTA);
  WiFi.setAutoReconnect(true);
  WiFi.waitForConnectResult(10000);
  WiFi.softAPConfig(
    IPAddress(1, 1, 1, 1),       // IP
    IPAddress(1, 1, 1, 1),       // gateway
    IPAddress(255, 255, 255, 0)  // subnet
  );
  WiFi.softAP("super secret hackathon network", "you should not know this password");
  server.on("/", HTTP_GET, onDataReceived);
  server.begin();
  isConnected = true;
}
void loop() {
  // HTTP requests verwerken
  server.handleClient();
  String codeStr = "";
  for (uint8_t i = 0; i < 4; i++) {
    codeStr += String(secret[i]);
  }
  // Buzzer feedback verwerken
  if (buzzerActive && millis() - lastBuzzTime >= buzzInterval) {
    lastBuzzTime = millis();
    if (buzzerOn) {
      noTone(buzzer);
      buzzerOn = false;
      buzzCount++;
      if (buzzCount >= buzzTarget) {
        buzzerActive = false;
      }
    } else {
      tone(buzzer, 2000);
      buzzerOn = true;
    }
  }
  // Display update
  display.clear();
  display.setFont(ArialMT_Plain_10);
  display.setTextAlignment(TEXT_ALIGN_LEFT);
  display.drawString(X0,      Y_LINE1, "Voer code in:");
  String s = "";
  for (uint8_t i = 0; i < 4; i++) {
    s += String(digits[i]);
    s += " ";
  }
  display.drawString(X0, Y_LINE2, s);
  display.drawString(X0, Y_LINE3, "Druk OK om te verzenden");
  display.display();
  // Knop input verwerken met debounce
  for (uint8_t i = 0; i < 4; i++) {
    bool reading = digitalRead(digitPins[i]);
    if (reading != lastState[i] && (millis() - lastDebounceTime[i]) > DEBOUNCE_MS) {
      lastDebounceTime[i] = millis();
      if (reading == LOW) {
        digits[i] = (digits[i] < 9) ? digits[i] + 1 : 1;
      }
    }
    lastState[i] = reading;
  }
  // OK knop verwerking
  bool readingOK = digitalRead(okPin);
  if (readingOK != lastState[4] && (millis() - lastDebounceTime[4]) > DEBOUNCE_MS) {
    lastDebounceTime[4] = millis();
    if (readingOK == LOW) {
      while (digitalRead(okPin) == LOW) delay(10);
      // Code controleren
      bool match = true;
      for (uint8_t i = 0; i < 4; i++) {
        if (digits[i] != secret[i]) { match = false; break; }
      }
      // Resultaat tonen
      display.clear();
      display.setTextAlignment(TEXT_ALIGN_CENTER);
      display.setFont(ArialMT_Plain_16);
      display.drawString(64, 28, match ? "OK" : "FOUT");
      display.display();
      // Reset input
      for (uint8_t i = 0; i < 4; i++) {
        digits[i] = 1;
      }
    }
  }
  lastState[4] = readingOK;
}
Accelerometer (client_accelerometer.ino)
#include "Arduino.h"
#include "ESP8266WiFi.h"
#include <Wire.h>
#include "heltec.h"
#include <MPU6050.h>
MPU6050 mpu;
// Bewegingsdetectie variabelen
int shakeCount = 0;
bool canDetect = true;
unsigned long lastShakeTime = 0;
// Netwerk configuratie
const char *host = "1.1.1.1"; // IP van de game master server
WiFiClient client;
bool isConnected = false;
// Functie voor het tonen van tekst op de OLED display
void displayText(String text) {
	Heltec.display->clear();
  Heltec.display->drawString(0, 0, text);
  Heltec.display->display();
}
// Display initialisatie
void setupDisplay() {
  Heltec.display->init();
  Heltec.display->clear();
  Heltec.display->display();
}
void setup() {
  Serial.begin(115200);
  setupDisplay();
  // WiFi verbinding configureren als client
  WiFi.disconnect(true);
  delay(100);
  WiFi.mode(WIFI_STA); // Station mode, verbind als client
  WiFi.setAutoReconnect(true);
  // Statisch IP adres toewijzen
  WiFi.config(
    IPAddress(1, 1, 1, 2),      // IP adres van deze doos
    IPAddress(1, 1, 1, 1),      // Gateway (game master)
    IPAddress(255, 255, 255, 0) // Subnet mask
  );
  
  displayText("Connected to AP\nCurrent IP: " + WiFi.localIP().toString());
  WiFi.begin("super secret hackathon network", "you should not know this password");
  // Wacht op WiFi verbinding met timeout
  int count = 0;
  while(WiFi.status() != WL_CONNECTED && count < 100) {
    count++;
    delay(500);
    displayText("Connecting to AP...");
  }
  // Controleer verbindingsstatus
  if(WiFi.status() == WL_CONNECTED) {
    displayText("Connected to AP\nCurrent IP: " + WiFi.localIP().toString());
    isConnected = true;
  } else {
    displayText("Connecting...Failed");
    return;
  }
  // MPU6050 accelerometer initialiseren
  mpu.initialize();
  if (!mpu.testConnection()) {
    Serial.println("MPU6050 NIET gevonden!");
    while (1);
  } else {
    Serial.println("MPU6050 verbonden.");
  }
}
// HTTP GET request naar game master voor communicatie
void sendDataToHost(String message) {
  if (client.connected()) {
    client.stop();
  }
  if (!client.connect(host, 80)) {
    Serial.println("Connect host failed!");
    return;
  }
  Serial.println("host Connected!");
  // Verstuur data als URL parameter
  String getUrl = "/?data=";
  getUrl += message;
  client.print(String("GET ") + getUrl + " HTTP/1.1\r\n" + "Host: " + host + "\r\n" + "Connection: close\r\n\r\n");
  Serial.println("Get send");
  client.stop();
  Serial.println("Connection closed");
}
void loop() {
  if (!isConnected) {
    return;
  }
  // Lees accelerometer waarden (X, Y, Z as)
  int16_t ax, ay, az;
  mpu.getAcceleration(&ax, &ay, &az);
  unsigned long now = millis();
  // Detecteer schudbeweging op Z-as met threshold
  if (canDetect && abs(az) > 22000) {
    delay(100);  // Debounce vertraging
    
    // Hercontroleer accelerometer waarde
    mpu.getAcceleration(&ax, &ay, &az);
    
    // Valideer dat beweging is gestopt (terugkeer naar rustpositie)
    if (abs(az) < 12000) {
      shakeCount++;
      Serial.print("Schud #");
      Serial.println(shakeCount);
      
      // Voorkom meerdere detecties door tijdelijke blokkering
      canDetect = false;
      lastShakeTime = now;
    }
  }
  // Heractiveer detectie na korte pauze
  if (!canDetect && now - lastShakeTime > 50) {
    canDetect = true;
  }
  // Controleer of uitdaging voltooid is (3 schudden)
  if (shakeCount >= 3) {
    Serial.println("✅ 3 keer geschud!");
    shakeCount = 0;
    
    // Stuur voltooiing naar game master
    sendDataToHost("1_complete");
    delay(7500); // Pauze voor volgende uitdaging
  }
  delay(50); // Korte vertraging voor stabiele werking
}Licht Sensor (client_licht.ino)
#include <Arduino.h>
#include <Wire.h>
#include "WiFi.h"
#include "HT_SSD1306Wire.h"
// Pin configuratie voor licht sensor
const int lichtSensorPin = 1;
// OLED display initialisatie
SSD1306Wire  factory_display(0x3c, 500000, SDA_OLED, SCL_OLED, GEOMETRY_128_64, RST_OLED);
// Netwerk configuratie
const char *host = "1.1.1.1"; // IP van de game master server
WiFiClient client;
bool isConnected = false;
// Functie voor het tonen van tekst op de OLED display
void displayText(String text) {
  factory_display.clear();
  factory_display.drawString(0, 0, text);
  factory_display.display();
}
// Display initialisatie
void setupDisplay() {
	factory_display.init();
	factory_display.clear();
	factory_display.display();
}
void setup() {
  Serial.begin(115200);
  setupDisplay();
  // WiFi verbinding configureren
  WiFi.disconnect(true);
  delay(100);
  WiFi.mode(WIFI_STA); // Station mode, verbind als client
  WiFi.setAutoReconnect(true);
  // Statisch IP adres toewijzen
  WiFi.config(
    IPAddress(1, 1, 1, 3),      // IP adres van deze doos
    IPAddress(1, 1, 1, 1),      // Gateway (game master)
    IPAddress(255, 255, 255, 0) // Subnet mask
  );
  displayText("Connected to AP\nCurrent IP: " + WiFi.localIP().toString());
  WiFi.begin("super secret hackathon network", "you should not know this password");
  
  // Wacht op WiFi verbinding met timeout
  int count = 0;
  while(WiFi.status() != WL_CONNECTED && count < 100) {
    count++;
    delay(500);
    displayText("Connecting to AP...");
  }
  // Controleer verbindingsstatus
  if(WiFi.status() == WL_CONNECTED) {
    displayText("Connected to AP\nCurrent IP: " + WiFi.localIP().toString());
    isConnected = true;
  } else {
    displayText("Connecting...Failed");
  }
  // Configureer licht sensor pin als input
  pinMode(lichtSensorPin, INPUT);
}
// HTTP GET request naar game master voor communicatie
void sendDataToHost(String message) {
  if (client.connected()) {
    client.stop();
  }
  if (!client.connect(host, 80)) {
    Serial.println("Connect host failed!");
    return;
  }
  Serial.println("host Connected!");
  // Verstuur data als URL parameter
  String getUrl = "/?data=";
  getUrl += message;
  client.print(String("GET ") + getUrl + " HTTP/1.1\r\n" + "Host: " + host + "\r\n" + "Connection: close\r\n\r\n");
  Serial.println("Get send");
  client.stop();
  Serial.println("Connection closed");
}
void loop() {
  if (!isConnected) {
    return;
  }
  // Lees digitale waarde van licht sensor
  int sensorWaarde = digitalRead(lichtSensorPin);
  // Controleer lichtintensiteit (HIGH = geen licht, LOW = licht gedetecteerd)
  if (sensorWaarde == HIGH) {
    delay(100); // Wacht bij onvoldoende licht
  } else {
    // Licht gedetecteerd - stuur voltooiing naar game master
    sendDataToHost("2_complete");
    delay(7500); // Pauze voor volgende uitdaging
  }
}
Temperatuur (client_temperatuur.ino)
#include "Arduino.h"
#include "WiFi.h"
#include "HT_SSD1306Wire.h"
// Pin configuratie voor temperatuur sensor
const int temperatuurSensorPin = 1;
// OLED display initialisatie
SSD1306Wire  factory_display(0x3c, 500000, SDA_OLED, SCL_OLED, GEOMETRY_128_64, RST_OLED);
// Netwerk configuratie
const char *host = "1.1.1.1";
WiFiClient client;
bool isConnected = false;
// Functie voor het tonen van tekst op de OLED display
void displayText(String text) {
  factory_display.clear();
  factory_display.drawString(0, 0, text);
  factory_display.display();
}
// Display initialisatie
void setupDisplay() {
	factory_display.init();
	factory_display.clear();
	factory_display.display();
}
void setup() {
  Serial.begin(115200);
  setupDisplay();
  // WiFi verbinding configureren
  WiFi.disconnect(true);
  delay(100);
  WiFi.mode(WIFI_STA);
  WiFi.setAutoReconnect(true);
  // Statisch IP adres toewijzen
  WiFi.config(
    IPAddress(1, 1, 1, 4),      // IP adres van deze doos
    IPAddress(1, 1, 1, 1),      // Gateway (game master)
    IPAddress(255, 255, 255, 0) // Subnet mask
  );
  displayText("Connected to AP\nCurrent IP: " + WiFi.localIP().toString());
  WiFi.begin("super secret hackathon network", "you should not know this password");
  
  // Wacht op WiFi verbinding met timeout
  int count = 0;
  while(WiFi.status() != WL_CONNECTED && count < 100) {
    count++;
    delay(500);
    displayText("Connecting to AP...");
  }
  // Controleer verbindingsstatus
  if(WiFi.status() == WL_CONNECTED) {
    displayText("Connected to AP\nCurrent IP: " + WiFi.localIP().toString());
    isConnected = true;
  } else {
    displayText("Connecting...Failed");
  }
}
// Configuratie voor gemiddelde temperatuur berekening
const int aantalMetingen = 30;
float temperatuurMeting[aantalMetingen];
int metingIndex = 0;
// HTTP GET request naar game master voor communicatie
void sendDataToHost(String message) {
  if (client.connected()) {
    client.stop();
  }
  if (!client.connect(host, 80)) {
    Serial.println("Connect host failed!");
    return;
  }
  Serial.println("host Connected!");
  // Verstuur data als URL parameter
  String getUrl = "/?data=";
  getUrl += message;
  client.print(String("GET ") + getUrl + " HTTP/1.1\r\n" + "Host: " + host + "\r\n" + "Connection: close\r\n\r\n");
  Serial.println("Get send");
  client.stop();
  Serial.println("Connection closed");
}
void loop() {
  if (!isConnected) {
    return;
  }
  // Lees analoge waarde van temperatuur sensor
  float sensorWaarde = analogRead(temperatuurSensorPin);
  // Converteer ADC waarde naar voltage (0-5V bereik voor 12-bit ADC)
  float voltage = sensorWaarde * (5 / 4095.0);
  
  // Converteer voltage naar temperatuur in Celsius
  float temperatureC = (voltage * 0.5) * 100.0;
  // Sla meting op in ringbuffer voor gemiddelde berekening
  temperatuurMeting[metingIndex] = temperatureC;
  metingIndex = (metingIndex + 1) % aantalMetingen;
  // Bereken gemiddelde temperatuur na volledige cyclus
  if (metingIndex == 0) {
    float sum = 0;
    for (int i = 0; i < aantalMetingen; i++) {
      sum += temperatuurMeting[i];
    }
    float averageTemp = sum / aantalMetingen;
    Serial.print("Average Temperature: ");
    Serial.println(averageTemp);
    // Controleer of temperatuur drempel bereikt is (24°C)
    if (averageTemp >= 24.00) {
      sendDataToHost("3_complete");
      delay(7500); // Pauze voor volgende uitdaging
    }
  }
  delay(100); // Korte vertraging tussen metingen
}
Geluid (client_geluid.ino)
#include "WiFi.h"
#include "HT_SSD1306Wire.h"
// Pin configuratie voor geluid sensor
const int geluidSensorPin = 1;
// OLED display initialisatie
SSD1306Wire  factory_display(0x3c, 500000, SDA_OLED, SCL_OLED, GEOMETRY_128_64, RST_OLED);
// Netwerk configuratie
const char *host = "1.1.1.1";
WiFiClient client;
bool isConnected = false;
// Functie voor het tonen van tekst op de OLED display
void displayText(String text) {
  factory_display.clear();
  factory_display.drawString(0, 0, text);
  factory_display.display();
}
// Display initialisatie
void setupDisplay() {
  factory_display.init();
  factory_display.clear();
  factory_display.display();
}
void setup() {
  Serial.begin(115200);
  setupDisplay();
  // WiFi verbinding configureren
  WiFi.disconnect(true);
  delay(100);
  WiFi.mode(WIFI_STA);
  WiFi.setAutoReconnect(true);
  // Statisch IP adres toewijzen
  WiFi.config(
    IPAddress(1, 1, 1, 5),      // IP adres van deze doos (aangepast voor geluid)
    IPAddress(1, 1, 1, 1),      // Gateway (game master)
    IPAddress(255, 255, 255, 0) // Subnet mask
  );
  displayText("Connected to AP\nCurrent IP: " + WiFi.localIP().toString());
  WiFi.begin("super secret hackathon network", "you should not know this password");
  
  // Wacht op WiFi verbinding met timeout
  int count = 0;
  while(WiFi.status() != WL_CONNECTED && count < 100) {
    count++;
    delay(500);
    displayText("Connecting to AP...");
  }
  // Controleer verbindingsstatus
  if(WiFi.status() == WL_CONNECTED) {
    displayText("Connected to AP\nCurrent IP: " + WiFi.localIP().toString());
    isConnected = true;
  } else {
    displayText("Connecting...Failed");
  }
  // Configureer geluid sensor pin als input
  pinMode(geluidSensorPin, INPUT);
}
// HTTP GET request naar game master voor communicatie
void sendDataToHost(String message) {
  if (client.connected()) {
    client.stop();
  }
  if (!client.connect(host, 80)) {
    Serial.println("Connect host failed!");
    return;
  }
  Serial.println("host Connected!");
  // Verstuur data als URL parameter
  String getUrl = "/?data=";
  getUrl += message;
  client.print(String("GET ") + getUrl + " HTTP/1.1\r\n" + "Host: " + host + "\r\n" + "Connection: close\r\n\r\n");
  Serial.println("Get send");
  client.stop();
  Serial.println("Connection closed");
}
void loop() {
  if (!isConnected) {
    return;
  }
  // Lees digitale waarde van geluid sensor
  int sensorWaarde = digitalRead(geluidSensorPin);
  // Controleer geluidsniveau (HIGH = stil, LOW = geluid >80dB gedetecteerd)
  if (sensorWaarde == HIGH) {
    delay(100); // Wacht bij stilte
  } else {
    // Geluid boven drempel gedetecteerd - stuur voltooiing naar game master
    sendDataToHost("4_complete");
    delay(7500); // Pauze voor volgende uitdaging
  }
}
Logica Schema's
Flowcharts die de programmalogica en beslissingsprocessen van elk systeem illustreren
System Overzicht
ArchitectuurCompleet systeemarchitectuur met Game Master, vier sensor-clients en WiFi-communicatieprotocol
Game Master (Knoppen)
Main ControllerGame Master-logica: WiFi AP-setup, web request-handling, knop-input met debounce en codevalidatie
Accelerometer Client
BewegingBewegingsdetectie met MPU6050: threshold-detectie, debounce-filtering en 3-shake validatie
Temperatuur Client
TemperatuurTemperatuurmeting: ADC-conversie, ring buffer-averaging (30 samples) en 24°C threshold-detectie
Licht Client
LichtLDR-lichtsensor: digitale pin-reading en directe lichtdetectie (LOW = licht aanwezig)
Geluid Client
AudioMicrofoon-geluidssensor: digitale threshold-detectie voor geluiden boven 80dB-niveau
Media Galerij
Foto's en video's van het Interactieve Escape Dozen project
Foto's
 
                
              De eerste houten plaat
 
                
              De houten platen voor de 5 dozen
 
                
              Lichtsensor doos
 
                
              Temperatuursensor doos
 
                
              Accelerometer doos
.jpeg) 
                
              Knoppen systeem met buzzer
 
                
              Afgewerkte doos
Video's
Werking van de OLED-display
Eerste werkende puzzel met de accelerometer, met het afspelen van de bijhorende code
Puzzel met lichtdetectie, met het afspelen van de bijhorende code
Systeem voor het invoeren van de code